#ifndef GPS_PROTO_DIFF_ENCODE__
#define GPS_PROTO_DIFF_ENCODE__

#include "glog/logging.h"
#include "gps.pb.h"

namespace gps_proto {

class DiffEncode {
 public:
  // Do the math as 64bit ints.
  static const int kScaleDouble = 1000 * 1000 * 1000;
  static int32_t DiffEncodeDouble(double base, double next) {
    int64_t base_int64 = base * kScaleDouble;
    int64_t next_int64 = next * kScaleDouble;
    // XXX reject results exceeding int32_t capacity
    return next_int64 - base_int64;
  }
  static double DiffDecodeDouble(double base, int32_t diff) {
    int64_t base_int64 = base * kScaleDouble;
    int64_t abs = base_int64 + diff;
    return static_cast<double>(abs) / kScaleDouble;
  }

  // Differentially encode the lat, lon fields of each <trkpt> in the <trkseg>.
  static void DiffEncodeTrkSeg(SegmentProto* segment_proto) {
    for (int i = segment_proto->point_size(); i-- > 2; ) {
      const PointProto& prev_point = segment_proto->point(i-1);
      PointProto* this_point = segment_proto->mutable_point(i);

      this_point->set_lon_diff(
        DiffEncodeDouble(prev_point.lon(), this_point->lon()));
      this_point->clear_lon();
      VLOG(2) << "lon diff " << this_point->lon_diff();
      this_point->set_lat_diff(
        DiffEncodeDouble(prev_point.lat(), this_point->lat()));
      this_point->clear_lat();
      VLOG(2) << "lat diff " << this_point->lat_diff();
    }
  }

  static void DiffDecodePoint(const PointProto& base, PointProto* next) {
    if (next->has_lon_diff() && base.has_lon()) {
      next->set_lon(DiffDecodeDouble(base.lon(), next->lon_diff()));
      next->clear_lon();
    }
    if (next->has_lat_diff() && base.has_lat()) {
      next->set_lat(DiffDecodeDouble(base.lat(), next->lat_diff()));
      next->clear_lat();
    }
  }

  static void DiffDecodeTrkSeg(SegmentProto* trk_seg_proto) {
    for (int i = 1; i < trk_seg_proto->point_size(); ++i) {
      const PointProto& prev_point = trk_seg_proto->point(i-1);
      PointProto* this_point = trk_seg_proto->mutable_point(i);
      DiffDecodePoint(prev_point, this_point);
    }
  }
};

}  // end namespace gps_proto

#endif  // GPS_PROTO_DIFF_ENCODE__
